{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qFdPvlXBOdUN"
      },
      "source": [
        "# Random number generation"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/random_numbers\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />View on TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/en/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BlGY1iiph_C2"
      },
      "source": [
        "TensorFlow provides a set of pseudo-random number generators (RNG), in the `tf.random` module. This document describes how you can control the random number generators, and how these generators interact with other tensorflow sub-systems. \n",
        "\n",
        "TensorFlow provides two approaches for controlling the random number generation process:\n",
        "\n",
        "1. Through the explicit use of `tf.random.Generator` objects. Each such object maintains a state (in `tf.Variable`) that will be changed after each number generation.\n",
        "\n",
        "2. Through the purely-functional stateless random functions like `tf.random.stateless_uniform`. Calling these functions with the same arguments (which include the seed) and on the same device will always produce the same results.\n",
        "\n",
        "Warning: The old RNGs from TF 1.x such as `tf.random.uniform` and `tf.random.normal` are not yet deprecated but strongly discouraged.\n",
        "\n",
        "Warning: The random numbers are not guaranteed to be consistent across TensorFlow versions, see: [Version Compatibility](https://www.tensorflow.org/guide/versions#what_is_not_covered)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zIGh9faCOp6x"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ECDrttf0s8Nu"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "# Creates 2 virtual devices cpu:0 and cpu:1 for using distribution strategy\n",
        "physical_devices = tf.config.list_physical_devices(\"CPU\")\n",
        "tf.config.experimental.set_virtual_device_configuration(\n",
        "    physical_devices[0], [\n",
        "        tf.config.experimental.VirtualDeviceConfiguration(),\n",
        "        tf.config.experimental.VirtualDeviceConfiguration()\n",
        "    ])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eqMlrUsVu2Ai"
      },
      "source": [
        "## The `tf.random.Generator` class\n",
        "\n",
        "The `tf.random.Generator` class is used in cases where you want each RNG call to produce different results. It maintains an internal state (managed by a `tf.Variable` object) which will be updated every time random numbers are generated. Because the state is managed by `tf.Variable`, it enjoys all facilities provided by `tf.Variable` such as easy checkpointing, automatic control-dependency and thread safety.\n",
        "\n",
        "You can get a `tf.random.Generator` by manually creating an object of the class or call `tf.random.get_global_generator()` to get the default global generator:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7yU1E3JvxOQD"
      },
      "outputs": [],
      "source": [
        "g1 = tf.random.Generator.from_seed(1)\n",
        "print(g1.normal(shape=[2, 3]))\n",
        "g2 = tf.random.get_global_generator()\n",
        "print(g2.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QmRCeAvTxulW"
      },
      "source": [
        "There are multiple ways to create a generator object. The easiest is `Generator.from_seed`, as shown above, that creates a generator from a seed. A seed is any non-negative integer. `from_seed` also takes an optional argument `alg` which is the RNG algorithm that will be used by this generator:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kISbOE4Xfjhv"
      },
      "outputs": [],
      "source": [
        "g1 = tf.random.Generator.from_seed(1, alg='philox')\n",
        "print(g1.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_mCRaN7dfd8j"
      },
      "source": [
        "See the *Algorithms* section below for more information about it.\n",
        "\n",
        "Another way to create a generator is with `Generator.from_non_deterministic_state`. A generator created this way will start from a non-deterministic state, depending on e.g. time and OS."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gxPLCLsz00qY"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_non_deterministic_state()\n",
        "print(g.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zSAp2BMj1JZ6"
      },
      "source": [
        "There are yet other ways to create generators, such as from explicit states, which are not covered by this guide.\n",
        "\n",
        "When using `tf.random.get_global_generator` to get the global generator, you need to be careful about device placement. The global generator is created (from a non-deterministic state) at the first time `tf.random.get_global_generator` is called, and placed on the default device at that call. So, for example, if the first site you call `tf.random.get_global_generator` is within a `tf.device(\"gpu\")` scope, the global generator will be placed on the GPU, and using the global generator later on from the CPU will incur a GPU-to-CPU copy.\n",
        "\n",
        "There is also a function `tf.random.set_global_generator` for replacing the global generator with another generator object. This function should be used with caution though, because the old global generator may have been captured by a `tf.function` (as a weak reference), and replacing it will cause it to be garbage collected, breaking the `tf.function`. A better way to reset the global generator is to use one of the \"reset\" functions such as `Generator.reset_from_seed`, which won't create new generator objects."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "324S5bpd9HRg"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "print(g.normal([]))\n",
        "print(g.normal([]))\n",
        "g.reset_from_seed(1)\n",
        "print(g.normal([]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z9H0wuvp9VwH"
      },
      "source": [
        "### Creating independent random-number streams\n",
        "\n",
        "In many applications one needs multiple independent random-number streams, independent in the sense that they won't overlap and won't have any statistically detectable correlations. This is achieved by using `Generator.split` to create multiple generators that are guaranteed to be independent of each other (i.e. generating independent streams)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Vg5_KN18OZjo"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "print(g.normal([]))\n",
        "new_gs = g.split(3)\n",
        "for new_g in new_gs:\n",
        "  print(new_g.normal([]))\n",
        "print(g.normal([]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dqOaGVzKOsRJ"
      },
      "source": [
        "`split` will change the state of the generator on which it is called (`g` in the above example), similar to an RNG method such as `normal`. In addition to being independent of each other, the new generators (`new_gs`) are also guaranteed to be independent of the old one (`g`).\n",
        "\n",
        "Spawning new generators is also useful when you want to make sure the generator you use is on the same device as other computations, to avoid the overhead of cross-device copy. For example: "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5jSnJBlUQzF3"
      },
      "outputs": [],
      "source": [
        "with tf.device(\"cpu\"):  # change \"cpu\" to the device you want\n",
        "  g = tf.random.get_global_generator().split(1)[0]  \n",
        "  print(g.normal([]))  # use of g won't cause cross-device copy, unlike the global generator"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sCxbccYMRdd4"
      },
      "source": [
        "Note: In theory, you can use constructors such as `from_seed` instead of `split` here to obtain a new generator, but by doing so you lose the guarantee that the new generator is independent of the global generator. You will also run the risk that you may accidentally create two generators with the same seed or with seeds that lead to overlapping random-number streams.\n",
        "\n",
        "You can do splitting recursively, calling `split` on splitted generators. There are no limits (barring integer overflow) on the depth of recursions."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8JUgnQM_O0lg"
      },
      "source": [
        "### Interaction with `tf.function`\n",
        "\n",
        "`tf.random.Generator` obeys the same rules as `tf.Variable` when used with `tf.function`. This includes three aspects."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jnSjhY6WM-J8"
      },
      "source": [
        "#### Creating generators outside `tf.function` \n",
        "\n",
        "`tf.function` can use a generator created outside of it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a5EEy0E2UHMw"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "@tf.function\n",
        "def foo():\n",
        "  return g.normal([])\n",
        "print(foo())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L_8kC7kbO5uu"
      },
      "source": [
        "The user needs to make sure that the generator object is still alive (not garbage-collected) when the function is called."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PwIrBv_zUYwI"
      },
      "source": [
        "#### Creating generators inside `tf.function` \n",
        "\n",
        "Creation of generators inside a `tf.function` can only happend during the first run of the function. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3JzpUvqJU4MW"
      },
      "outputs": [],
      "source": [
        "g = None\n",
        "@tf.function\n",
        "def foo():\n",
        "  global g\n",
        "  if g is None:\n",
        "    g = tf.random.Generator.from_seed(1)\n",
        "  return g.normal([])\n",
        "print(foo())\n",
        "print(foo())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UaTVnOhHVM9a"
      },
      "source": [
        "#### Passing generators as arguments to `tf.function`\n",
        "\n",
        "When used as an argument to a `tf.function`, different generator objects with the same state size (state size is determined by the RNG algorithm) won't cause retracing of the `tf.function`, while those with different state sizes will."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DeR9kvt0V-ad"
      },
      "outputs": [],
      "source": [
        "num_traces = 0\n",
        "@tf.function\n",
        "def foo(g):\n",
        "  global num_traces\n",
        "  num_traces += 1\n",
        "  return g.normal([])\n",
        "foo(tf.random.Generator.from_seed(1))\n",
        "foo(tf.random.Generator.from_seed(2))\n",
        "print(num_traces)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fxcS6IY8WZuh"
      },
      "source": [
        "### Interaction with distribution strategies\n",
        "\n",
        "There are three ways in which `Generator` interacts with distribution strategies."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GyZv9QJkZfkQ"
      },
      "source": [
        "#### Creating generators outside distribution strategies\n",
        "\n",
        "If a generator is created outside strategy scopes, all replicas’ access to the generator will be serialized, and hence the replicas will get different random numbers."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HX_beT9SZWMp"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "with strat.scope():\n",
        "  def f():\n",
        "    print(g.normal([]))\n",
        "  results = strat.run(f)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ydYQbUqLPAgH"
      },
      "source": [
        "Note that this usage may have performance issues because the generator's device is different from the replicas."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Yal4LbBKbAeN"
      },
      "source": [
        "#### Creating generators inside distribution strategies\n",
        "\n",
        "Creating generators inside strategy scopes is disallowed, because there is ambiguity on how to replicate a generator (e.g. should it be copied so that each replica gets the same random numbers, or 'split' so that each replica gets different random numbers)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T6McVq-gbK_d"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "with strat.scope():\n",
        "  try:\n",
        "    tf.random.Generator.from_seed(1)\n",
        "  except ValueError as e:\n",
        "    print(\"ValueError:\", e)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pqQfWkMWQnnI"
      },
      "source": [
        "Note that `Strategy.run` will run its argument function in a strategy scope implicitly:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "X6Ceqha3RKKo"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "def f():\n",
        "  tf.random.Generator.from_seed(1)\n",
        "try:\n",
        "  strat.run(f)\n",
        "except ValueError as e:\n",
        "  print(\"ValueError:\", e)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ODLS8njzbUEF"
      },
      "source": [
        "#### Passing generators as arguments to `Strategy.run`\n",
        "\n",
        "If you want each replica to use its own generator, you need to make `n` generators (either by copying or splitting), where `n` is the number of replicas, and then pass them as arguments to `Strategy.run`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YurAsX3nbROP"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "gs = tf.random.get_global_generator().split(2)\n",
        "# to_args is a workaround for the absence of APIs to create arguments for \n",
        "# run. It will be replaced when such APIs are available.\n",
        "def to_args(gs):  \n",
        "  with strat.scope():\n",
        "    def f():\n",
        "      return [gs[tf.distribute.get_replica_context().replica_id_in_sync_group]]\n",
        "    return strat.run(f)\n",
        "args = to_args(gs)\n",
        "def f(g):\n",
        "  print(g.normal([]))\n",
        "results = strat.run(f, args=args)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "73an1POpsi6V"
      },
      "source": [
        "## Stateless RNGs\n",
        "\n",
        "Usage of stateless RNGs is simple. Since they are just pure functions, there is no state or side effect involved."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0-aOOA3gasn_"
      },
      "outputs": [],
      "source": [
        "print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))\n",
        "print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2O_D-RAFNH2Q"
      },
      "source": [
        "Every stateless RNG requires a `seed` argument, which needs to be an integer Tensor of shape `[2]`. The results of the op are fully determined by this seed."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4BvGkPnaOUPF"
      },
      "source": [
        "## Algorithms"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "58-8kvR4pRwO"
      },
      "source": [
        "### General\n",
        "\n",
        "Both the `tf.random.Generator` class and the `stateless` functions support the Philox algorithm (written as `\"philox\"` or `tf.random.Algorithm.PHILOX`) on all devices.\n",
        "\n",
        "Different devices will generate the same integer numbers, if using the same algorithm and starting from the same state. They will also generate \"almost the same\" float-point numbers, though there may be small numerical discrepancies caused by the different ways the devices carry out the float-point computation (e.g. reduction order)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WETA04F1OYPL"
      },
      "source": [
        "### XLA devices\n",
        "\n",
        "On XLA-driven devices (such as TPU, and also CPU/GPU when XLA is enabled) the ThreeFry algorithm (written as `\"threefry\"` or `tf.random.Algorithm.THREEFRY`) is also supported. This algorithm is fast on TPU but slow on CPU/GPU compared to Philox. "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c04JkebCPTPu"
      },
      "source": [
        "See paper ['Parallel Random Numbers: As Easy as 1, 2, 3'](https://www.thesalmons.org/john/random123/papers/random123sc11.pdf) for more details about these algorithms."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "random_numbers.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
